﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartMathLibrary.NonlinearEquationSolvers;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides the finding of all real inflection point of a polynomial.
    /// </summary>
    [Serializable]
    public class RealInflectionPointFinder
    {
        /// <summary>
        /// The polynomial for which the inflection point should be found.
        /// </summary>
        private SimplePolynomial polynomial;

        /// <summary>
        /// Initializes a new instance of the <see cref="RealInflectionPointFinder"/> class.
        /// </summary>
        /// <param name="polynomial">The polynomial for which the inflection point should be found.</param>
        public RealInflectionPointFinder(SimplePolynomial polynomial)
        {
            if (polynomial == (SimplePolynomial) null)
            {
                throw new ArgumentNullException("polynomial");
            }

            this.polynomial = polynomial;
        }

        /// <summary>
        /// Gets or sets the polynomial for which the inflection point should be found.
        /// </summary>
        /// <value>The polynomial for which the inflection point should be found.</value>
        public SimplePolynomial Polynomial
        {
            get { return polynomial; }
            set { polynomial = value; }
        }

        /// <summary>
        /// Finds the real inflection points of the specified polynomial.
        /// </summary>
        /// <returns>The real inflection points of the specified polynomial.</returns>
        public Point2D[] FindInflectionPoints()
        {
            return this.FindInflectionPoints(1e-15);
        }

        /// <summary>
        /// Finds the real inflection points of the specified polynomial.
        /// </summary>
        /// <param name="checkPrecision">The distance to check if the found point is really 
        /// an inflection points.</param>
        /// <returns>
        /// The real inflection points of the specified polynomial.
        /// </returns>
        public Point2D[] FindInflectionPoints(double checkPrecision)
        {
            if (this.polynomial.Degree < 3)
            {
                throw new ArgumentException("A polynomial with a minimum degree of three is required.");
            }

            List<Point2D> result = new List<Point2D>();
            SimplePolynomial workingPolynomial = this.polynomial.Copy().Derivative().Derivative();
            BairstowComplexRootFinder rootFinder = new BairstowComplexRootFinder(workingPolynomial);
            ComplexNumber[] rootResults = rootFinder.FindRoots();

            for (int i = 0; i < rootResults.Length; i++)
            {
                if (rootResults[i].ImaginaryNumberPart == 0)
                {
                    int a = Math.Sign(workingPolynomial.SolveAt(rootResults[i].RealNumberPart + checkPrecision));
                    int b = Math.Sign(workingPolynomial.SolveAt(rootResults[i].RealNumberPart - checkPrecision));

                    if (((a < 0) && (b > 0)) || ((a > 0) && (b < 0)))
                    {
                        result.Add(new Point2D(rootResults[i].RealNumberPart,
                                               this.polynomial.SolveAt(rootResults[i].RealNumberPart)));
                    }
                }
            }

            return result.ToArray();
        }
    }
}